/************************************************************************
* Programmer:  Philippe Gambling
*
* Course:  CSCI 4534
*
* Date:  April 15, 2009
*
* Assignment:  #4
*
* Environment:  GCC 3.2.2 running on diamond.rocks.uhcl.edu
*
* Files Included: server.c, pcb.h, queue.h, server
*
* Purpose:  Receives a PCB containing a CPU burst and memory request,
*           simulates a long-term scheduler that allocates memory to new processes,
*           simulates a first come first server scheduler to calculate start,
*           completion, waiting, and turnaround for each PCB received,
*           and returns each updated PCB to the client program that sent it.
*
* Input:  The number of expected clients entered by the user,
*         multiple PCB structs from each client program.
*
* Preconditions:  The server program is started before the clients.
*
* Output:  A memory map after each process is allocated memory.
*          The CPU utilization, average turnaround, and 
*          waiting times for all processes.
*
* Postconditions:  The server terminates after sending the result to each client.
*                  FIFO_TO_SERVER is deleted.
*
* Algorithm:
*          Prompt user for the number of clients, 
*           exit if less than 1 or greater than MAX_NUM_CLIENTS
*          Create FIFO_TO_SERVER
*          Open FIFO_TO_SERVER as read only
*          Loop until data read from the expected number of clients
*            Read a PCB struct from FIFO_TO_SERVER to the PCB array
*          Execute CPU scheduler with PCB array as a parameter
*            set clock to 0
*            loop while ReadyQueue not empty
*                       or RunState not clear
*                       or NewProcess queue not empty
*              Call LongTermScheduler:
*                while NewProcess not empty and memory available for next process
*                  remove front of NewProcess queue
*                  set process's StartTime to current clock value
*                  allocate memory for process
*                  print memory map
*                  enqueue process in ReadyState queue 
*              Exit LongTermScheduler
*              Call ShortTermScheduler:
*                if run state free and ReadyQueue is not empty
*                  get next PCB from ReadyQueue
*                  assign to run state
*                increment the system clock
*                iterate ReadyQueue and increment each PCB's WaitTime
*                if run state free
*                  increment total idle time
*                else
*                  decrement current PCB's CPU Burst
*                  if current PCB's cpu burst equals 0
*                    set CompletionTime equal to clock
*                    set PCB's TurnaroundTime to CompletionTime
*                    add complete process's wait time to total wait time
*                    add complete process's turnaround time to total turnaround time
*                    remove process from memory
*                    clear run state variable
*              Exit ShortTermScheduler
*            Calculate average of all TurnaroundTime values in PCB array
*            Calculate average of all WaitTime values in PCB array
*            Calculate CPU utilization as 100*(clock - total idle time)/clock
*          Exit CPU scheduler
*          For each element in PCB array
*            Open ClientFifoName as write only (ClientFifoName is the private fifo 
*            name received from client)
*            Write the PCB to ClientFifoName
*            Close ClientFifoName
*          Print CPU utilization, average turnaround time, and average wait time
*          Close FIF0_TO_SERVER
*          Unlink FIFO_TO_SERVER
***********************************************************************/ 

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <limits.h>

/* Custom includes created for this assignment */
#include "queue/queue.h"
#include "memory/memory.h"
#include "pcb.h"  // Defines a struct to model a process control block

#define true 1
#define false 0
#define MAX_NUM_CLIENTS 25 // Maximum number of clients supported by server
#define OS_MEM_SIZE 4 // Amount of memory used by the OS

/* Function prototypes */
void RunSimulation(PCB [],float *, float *, float *);
void LongTermScheduler(const Memory, const Queue, const Queue, const int);
void ShortTermScheduler(const Queue, PCB **, const Memory, int *, int *, float *, float *); 

extern int errno;

int main(int argc, char * argv[])
{
  int fda;		// to read from client
  int fdb;		// to write to client
  int i;      // Loop counter   
  int numClients = 0; // Number of clients
  float averageWait = 0; // Average process wait time
  float averageTurnaround = 0; // Average process turnaround timeg for IO
  float cpuUtilization = 0; // CPU utilization
  char testOutputFile[25]; // Name of file to write test results to
  int testmode = 0; // Output extra information for testing
  
  PCB processes[MAX_NUM_CLIENTS+1]; // Holds all processes received from clients (the extra PCB marks the end of the array)
  for(i=1;i<=MAX_NUM_CLIENTS;i++) processes[i] = NEW_PCB; // Initialize the proccesses array
  
  // Get expected number of clients
  if(argc < 2) {
    printf("Usage: ./server NUM_CLIENTS\n");
    printf("      NUM_CLIENTS - The number of expected clients (positive whole number)\n");    
    exit(-1);
  }
  else {
    numClients = atoi(argv[1]);
    if(argc > 2) {
      strcpy(testOutputFile,argv[2]);
      testmode = 1;
    }
  }
  // Make sure number of clients is at least 1 and did not exceed MAX_NUM_CLIENTS
  if(numClients<0) 
  {
    perror("Number of clients must be at least 1, exiting...");
	  exit(-2);
  }
  else
  if(numClients>MAX_NUM_CLIENTS)
  {
    perror("Number of clients can not exceed maximum number of clients, exiting...");
	  exit(-3);
  }

  // Create the FIFO_TO_SERVER
  if ((mkfifo("FIFO_TO_SERVER",0666)<0 && errno != EEXIST))
  {
	  perror("cant create FIFO_TO_SERVER");
	  exit(-1);
  }

  // Open FIFO_TO_SERVER to receive data from clients
  if((fda=open("FIFO_TO_SERVER", O_RDONLY))<0)
	  printf("cant open FIFO_TO_SERVER to read");

  // Read each clients' PCB
  i = 0;

  while(i<numClients)
  {
    // Read data from client
    if(read(fda, &processes[i], sizeof(PCB))>0) 
    { 
      processes[i].Pid = i+'1'; // Re-numbering Pid to simplify memory map
      printf("Server: Received the following message from client: \n");
      PCB_Print(&processes[i]); 
      i++;  // Increment loop counter if more than 0 bytes read
    }
  }

  // Run processes through CPU scheduler  
  RunSimulation(processes,
                &cpuUtilization,
                &averageWait,
                &averageTurnaround);

  for(i=0;i<numClients;i++)
  {
    // Open ClientFifoName to send data to clients
    if((fdb=open(processes[i].ClientFifoName, O_WRONLY))<0) {
      printf("cant open %s to write", processes[i].ClientFifoName); }
    
    // Send the result back to each client
    write(fdb, &processes[i], sizeof(PCB));
    printf("Server: Sent the results to client\n");
    
    close(fdb);
  }
  printf("Server: CPU utilization = %.2f%%\n",cpuUtilization);
  printf("Server: Average turnaround time = %.2f\n",averageTurnaround);
  printf("Server: Average wait time = %.2f\n",averageWait);
  printf("Server: all done, Disconnecting.\n ");

  close(fda);
  unlink("FIFO_TO_SERVER");
  
  if(testmode) { // Print important data to a file
    FILE *file;
    file = fopen(testOutputFile,"w");
    fprintf(file,"Number of clients = %d\n",numClients);
    fprintf(file,"CPU utilization = %.2f%%\n",cpuUtilization);
    fprintf(file,"Average turnaround time = %.2f\n",averageTurnaround);
    fprintf(file,"Average wait time = %.2f\n",averageWait);       
    fclose(file);
  }
  
  return 0;
}


void RunSimulation(PCB processes[],float * cpuUtil, float * avgWait, float * avgTurn) {
/************************************************************************
* Purpose:  To simulate a CPU scheduler and memory.  
*
* Parameters: An array PCB structs, represents all processes.
*             float * cpuUtil, returns CPU utilization.
*             float * avgWait, returns average wait time.
*             float * avgTurn, return average turnaround time.
*
* Action:  All PCBs in processes array are executed in simulation and updated.
*
* Algorithm: First come first server scheduler.
***********************************************************************/ 
  Queue ReadyQueue = new_Queue(sizeof(PCB *)); // Models ready state as a queue of PCB pointers
  Queue NewQueue = new_Queue(sizeof(PCB *)); // Models new state as a queue of PCB pointers
  PCB * RunState = NULL; // Models running state
  PCB * nextProcess = &processes[0]; // Next arriving process
  int idleTime = 0;      // Total time run state is not in use
  int numProcesses = 0;  // Total processes arrived to scheduler
  int clock = 0; // Start system clock at 0
  
  Memory MainMemory = new_Memory(MAX_MEM_REQ+OS_MEM_SIZE); // Models system's main memory
  printf("Server: Initialized Main Memory\n");
  Memory_Print(MainMemory,NULL);
  Memory_Allocate(MainMemory,OS_MEM_SIZE,'0'); // Allocate memory for OS
  printf("Server: Allocated memory for OS (process 0)\n");  
  Memory_Print(MainMemory,NULL);  
  
  // Clear CPU scheduler metrics
  *cpuUtil = 0;
  *avgWait = 0;
  *avgTurn = 0;

  // Load up NewQueue
  while(nextProcess->Pid != '0') {
    Queue_AddBack(NewQueue,&nextProcess);
    numProcesses++;
    nextProcess = &processes[numProcesses];
  }  

  while(!Queue_Empty(ReadyQueue) ||   // Run until all waiting processes,
        !Queue_Empty(NewQueue) ||     // new processes,
        RunState != NULL)             // and process currently running complete
  {
    LongTermScheduler(MainMemory,NewQueue,ReadyQueue,clock);  // Simulates memory allocation and loading ReadyQueue
    ShortTermScheduler(ReadyQueue,&RunState,MainMemory,&clock,&idleTime,avgWait,avgTurn); // Simulates 1 CPU clock cycle
  }

  // Calculate CPU scheduler metrics
  *avgTurn/=numProcesses;
  *avgWait/=numProcesses;
  *cpuUtil = (clock-idleTime)*100.0/clock;
  
  // Clean up
  free_Queue(ReadyQueue);
  free_Queue(NewQueue);  
}

void LongTermScheduler(const Memory MainMemory, const Queue NewQueue, const Queue ReadyQueue, const int clock) 
{
/************************************************************************
* Purpose:  To simulate a loading new processes into memory
*
* Parameters: const Memory MainMemory - Simulated memory used for executing processes.
*             const Queue NewQueue - Queue of processes waiting for requested memory.
*             const Queue ReadyQueue - Queue of processes loaded into memory waiting for CPU.
*             const int clock - Current system clock time.
*
* Action:  Each new processes is assigned memory and moved to ReadyQueue until no
*          memory is available for the next process.
*
* Algorithm: First-fit memory allocation
*            (See LongTermScheduler in server preface)
***********************************************************************/
  PCB * temp = NULL;

  Queue_GetFront(NewQueue,&temp);
  while(!Queue_Empty(NewQueue) && Memory_Allocate(MainMemory,temp->MemReq,temp->Pid)) {
    Queue_RemoveFront(NewQueue,&temp);
    temp->StartTime = clock;
    Queue_AddBack(ReadyQueue,&temp);   
    printf("Server: Process %c loaded in memory and moved to ReadyQueue at %d\n",temp->Pid,clock);
    Memory_Print(MainMemory,NULL);
    Queue_GetFront(NewQueue,&temp);
  }
}

void ShortTermScheduler(const Queue ReadyQueue, PCB ** RunState, const Memory MainMemory, int * clock, int * idleTime, float * sumWait, float * sumTurn) 
{
/************************************************************************
* Purpose:  To simulate a CPU clock cycle.
*
* Parameters: const Queue ReadyQueue - Queue of processes loaded into memory waiting for CPU.
*             PCB * RunState - Currently process using the CPU.
*             const Memory MainMemory - Simulated memory used for executing processes.
*             int * clock - Pointer to current system clock.
*             int * idleTime - Pointer to running total of idle time.
*             float * sumWait - Pointer to running sum of wait times.
*             float * sumTurn - Pointer to running sum of turnaround times.
*
* Action:  Wait times for processes in ReadyQueue are updated, RunState process burst time is decremented,
*          clock is incremented, and processes completing their CPU burst are terminated.
*
* Algorithm: First come first server scheduler 
*            (See ShortTermScheduler in server preface)
***********************************************************************/
  PCB * temp = NULL;

  // if run state is free get next process from ReadyQueue and assign to run state
  if(*RunState == NULL && !Queue_Empty(ReadyQueue)) { // if run state is free
    // get next process from ReadyQueue and assign to run state
    Queue_RemoveFront(ReadyQueue,RunState); 
    printf("Server: Process %c moved to RunState at %d\n",(*RunState)->Pid,*clock);
  }
  // Increment the clock
  (*clock)++;
  // Iterate ReadyQueue and increment each waiting process's WaitTime
  Queue_StartIterator(ReadyQueue);
  while(Queue_NextNode(ReadyQueue,&temp) != EEND) {
    printf("Server: Incremented process %c wait time at %d\n",temp->Pid,*clock);
    temp->WaitTime++;
  } 
  if(*RunState == NULL) { // count cycle as idle if no process ready 
    (*idleTime)++;
    printf("Server: Idle cycle at %d\n", *clock);      
  }
  else { // cycle time spent on a process
    printf("Server: Process %c used a CPU cycle at %d\n",(*RunState)->Pid,*clock);      
    (*RunState)->CPUBurst--; // Decrement CPU Burst
    if((*RunState)->CPUBurst == 0) { // Process complete
      printf("Server: Process %c completed at %d\n",(*RunState)->Pid,*clock);
      (*RunState)->CompletionTime = *clock;
      (*RunState)->TurnaroundTime = (*RunState)->CompletionTime;
      *sumTurn += (*RunState)->TurnaroundTime;
      *sumWait += (*RunState)->WaitTime;
      Memory_Deallocate(MainMemory,(*RunState)->Pid);            
      *RunState = NULL;  // Process terminates
      Memory_Print(MainMemory,NULL);      
    }   
  }
}
